import { hasOwn, isFunction, isObject, isPlainObject } from "./Util";

export class Observer {
  value: any;
  vmCount: number;
  constructor(value, callback) {
    this.value = value;
    this.vmCount = 0;
    Object.defineProperty(value, "__ob__", {
      value: this,
      enumerable: false,
      writable: true,
      configurable: true,
    });
    const keys = Object.keys(value);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(value, keys[i], callback);
    }
  }
}
export function observe(
  value: any,
  callback: Function,
  asRootData?: boolean
): Observer | void {
  if (!isObject(value)) {
    return;
  }
  let ob: Observer | void = void 0;
  if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value)
  ) {
    ob = new Observer(value, callback);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob;
}
export function defineReactive(obj: Object, key: string, callback: Function) {
  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }
  const getter = property && property.get;
  const setter = property && property.set;
  let val = undefined;
  if (arguments.length > 1) {
    val = obj[key];
  }
  Object.defineProperty(obj, "_gsap", {
    enumerable: false,
    writable: true,
    configurable: true,
  });
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val;
      return value;
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val;
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      if (getter && !setter) return;
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
        if (isFunction(callback)) {
          callback(newVal, key);
        }
      }
      observe(newVal, (childVal, childKey) => {
        callback(newVal, key);
      });
    },
  });
  if (obj[key]) {
    const keys = Object.keys(obj[key]);
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj[key], keys[i], ()=>{callback(val,key)});
    }
  }
}
